package xyz.bali16.module.netdisk.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.gitee.starblues.bootstrap.annotation.AutowiredType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import xyz.bali16.application.core.enums.ApiEnum;
import xyz.bali16.application.core.model.Result;
import xyz.bali16.application.core.utils.IDUtils;
import xyz.bali16.module.netdisk.utils.PageUtils;
import xyz.bali16.application.core.utils.TimeUtil;
import xyz.bali16.module.netdisk.entity.FileFolder;
import xyz.bali16.module.netdisk.entity.FileStorage;
import xyz.bali16.module.netdisk.entity.FileUser;
import xyz.bali16.module.netdisk.entity.UploadFile;
import xyz.bali16.module.netdisk.enums.FilePermissionEnum;
import xyz.bali16.module.netdisk.model.vo.FileFolderFileVoConvert;
import xyz.bali16.module.netdisk.model.vo.FileVo;
import xyz.bali16.module.netdisk.model.vo.UploadFileFileVoConvert;
import xyz.bali16.module.netdisk.service.*;
import xyz.bali16.module.netdisk.utils.FileUtil;
import xyz.bali16.module.netdisk.utils.FtpUtil;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static xyz.bali16.application.core.model.Result.failure;
import static xyz.bali16.application.core.model.Result.success;


/**
 * @author bali2 on 7/11/2021.
 * @version 1.0
 */
@Service("NetdiskService")
public class NetdiskServiceImpl implements NetdiskService {

    @Autowired
    protected UploadFileService uploadFileService;

    @Autowired
    protected FileFolderService fileFolderService;

    @Autowired
    protected FileStorageService fileStorageService;

    @Autowired
    protected FileUserService fileUserService;

    boolean isRootDir(String id) {
        return id.equals("0");
    }

    boolean existSuffix(Integer index) {
        return index != -1;
    }

    boolean existFiles(List l) {
        return l.size() > 0;
    }

    boolean existFolders(List l) {
        return l.size() > 0;
    }

    @Override
    public Result<String> uploadFile(MultipartFile file, String fileUserId, String fileStorageId, String fileFolderId, HttpServletRequest request) {
        System.out.println("获取到的fileUserId " + fileUserId);
        // 获取文件用户
        FileUser fileUser = fileUserService.getById(fileUserId);

        System.out.println(fileUser);
        /**选择一个指定用户、且用户有权上传下载，可用的文件仓库*/
        FileStorage fileStorage = fileStorageService.getFileStorageByFileUserId(fileUser.getId(), fileStorageId);
        if (fileStorage == null) {
            return failure("用户没有上传文件的权限或文件仓库! 上传失败...");
        }
        // 不接受文件有空格
        String uploadFileName = file.getName().replaceAll(" ", "");

        /**判断仓库容量*/
        // long单位转int单位
        Integer uploadFileByteSize = Math.toIntExact(file.getSize() / 1024); //2047T空间可存放

        if (fileStorage.getCurrentSize() + uploadFileByteSize > fileStorage.getMaxSize()) {
            return failure("上传失败! 仓库已满。");
        }

        /**获取上传目录下所有文件，判断是否已存在*/
        List<UploadFile> uploadFiles = null;
        if (isRootDir(fileFolderId)) { // 如果文件传到根文件夹
            // 根据文件仓库获取根目录文件
            System.out.println("=测试" + fileFolderId);
            uploadFiles = uploadFileService.getRootFilesByFileStorageId(fileStorageId);
        } else { // 如果不是根文件夹
            // 根据文件夹id获取文件
            uploadFiles = uploadFileService.getFilesByParentFolderId(fileFolderId);
        }
        /**校验MD5，判断数据重复*/
        String uploadFileMD5 = FileUtil.generateFileMD5(file); // 生成文件MD5
        //boolean fileMd5Exist = uploadFiles.stream().anyMatch(f -> f.getMd5().equals(uploadFileMD5)&&fileUserId.equals(f.getUploadFileUserId())); // 判断
        for (UploadFile f : uploadFiles) {
            if (f.getMd5().equals(uploadFileMD5) && fileUserId.equals(f.getUploadFileUserId())) {
                System.out.println("文件存在,直接指向相同文件");
                return success(ApiEnum.UPLOAD_SUCCESS, (getFileUrl(f, request.getContextPath())));
            }
        }
        //if (fileMd5Exist) {
        //    return failure("当前文件已存在! 上传失败...");
        //}
        System.out.println("uploadFileName: " + uploadFileName);
        /**数据不重复时候处理文件名重复*/
        uploadFileName = FileUtil.autoRenameExistFileName(uploadFiles.stream().map(UploadFile::getName).collect(Collectors.toList()), uploadFileName);
        System.out.println("uploadFileName: " + uploadFileName);
        if (!FileUtil.verifyName(uploadFileName)) {
            return failure("上传失败! 文件名不符合规范...");
        }

        // 处理文件大小
        String byteSizeStr = String.valueOf(file.getSize() / 1024.0);
        int indexDot = byteSizeStr.lastIndexOf(".");
        byteSizeStr = byteSizeStr.substring(0, indexDot); //只要前面的字节数字 - 整型
        Integer byteSize = Integer.valueOf(byteSizeStr);
        // 获取文件后缀
        int index = uploadFileName.lastIndexOf(".");
        String fileNameSuffix = "";
        int type = 4;

        if (existSuffix(index)) {
            fileNameSuffix = uploadFileName.substring(index).toLowerCase(); //.开始拦截 .doc .html
            uploadFileName = uploadFileName.substring(0, index);
            //获得文件类型数字
            type = FileUtil.getFileType(fileNameSuffix); //判断大概的文件类型 如果不符合就是other类型
        } else {
            fileNameSuffix = ""; //无后缀情况
        }

        System.out.println("上传时候的文件名：" + uploadFileName);

        String UUID = IDUtils.uuid();

        try {
            //todo 用户多仓库的时候 就不能这样放了 不过其实也行 莫名之中就知道文件上传者在这仓库所有文件了
            String path = fileStorage.getId() + "/" + fileUser.getId() + "/" + fileFolderId; // 文件相对路径: [文件仓库id]/[系统用户id]/[文件夹id]
            // 设置文件存储的位置
            boolean uploadSuccess = FtpUtil.uploadFile("/" + path, UUID + fileNameSuffix, file.getInputStream()); //uploadFileName + fileNameSuffix

            if (uploadSuccess) {
                /**存储文件、调整容量*/
                UploadFile uploadFile = UploadFile.builder()
                        .name(uploadFileName).UUID(UUID).md5(uploadFileMD5).fileStorageId(fileUser.getFileStorageId()).path(path)
                        .downloadCount(0).gmtCreated(TimeUtil.getNowTimeToSeconds()).parentFolderId(fileFolderId)
                        .size(byteSize).type(type).suffix(fileNameSuffix).uploadFileUserId(fileUserId).build();
                uploadFileService.save(uploadFile);

                return success(ApiEnum.UPLOAD_SUCCESS, (getFileUrl(uploadFile, request.getContextPath())));
            } else {
                return failure("文件上传失败!" + file.getOriginalFilename());
            }
        } catch (IOException e) {
            e.printStackTrace();
            return failure(ApiEnum.UPLOAD_ERROR, "上传失败!", e);
        }
    }

    /**
     * @param fileUserId    文件用户Id
     * @param fileStorageId 文件仓库Id
     * @param fileId        文件Id
     * @param request
     * @param response
     */
    @Override
    public void downloadFile(String fileUserId, String fileStorageId, String fileId, HttpServletRequest request, HttpServletResponse response) {
        // 获取用户所有可用的文件仓库
        List<FileStorage> fileStorages = fileStorageService.getFileStoragesByPermission(fileUserId, FilePermissionEnum.UPLOAD_DOWNLOAD.getStatus());
        if (CollectionUtil.isEmpty(fileStorages)) {
            //logger.error("用户没有可用的文件仓库");
            return;
        }
        System.out.println(fileStorages);
        System.out.println(fileId);
        System.out.println(fileStorageId);
        // 在可用的文件仓库中查看是否有入参指定的文件仓库Id
        Optional<FileStorage> fileStorageOptional = fileStorages.stream().filter(fs -> fs.getId().equals(fileStorageId)).findFirst();
        if (!fileStorageOptional.isPresent()) {
            //logger.error("用户没有下载文件的权限!下载失败...");
            return;
        }
        // TODO 这里逻辑不对啊
        // 应该需要获取文件仓库下所有文件、包括文件夹内的文件的文件Id，与fileId进行匹对
        // 获取要下载的文件
        UploadFile uploadFile = uploadFileService.getById(fileId);
        if (uploadFile == null) {
            //logger.error("下载的文件不存在!下载失败...");
            return;
        }
        try {
            downloadFile(uploadFile, request, response);
        } catch (Exception e) {
//            return "fail";
        }
//        return "success";
    }

    /**
     * 根据文件仓库以及文件夹Id获取文件夹内的文件
     *
     * @param fileStorageId
     * @param fileFolderId
     * @param curPage
     * @param pageSize
     * @return
     */
    @Override
    public PageUtils list(String fileStorageId, String fileFolderId, Integer curPage, Integer pageSize) {
        List<FileFolder> folders = null;
        List<UploadFile> files = null;
        // 如果是文件夹
        if (fileFolderId != null) {
            folders = fileFolderService.getFileFoldersByParentFolderId(fileFolderId);
            files = uploadFileService.getFilesByParentFolderId(fileFolderId);
            folders = folders.stream().filter(f -> !f.getId().equals(fileFolderId)).collect(Collectors.toList());
            //
        } else {
            folders = fileFolderService.getRootFoldersByFileStorageId(fileStorageId);
            files = uploadFileService.getRootFilesByFileStorageId(fileStorageId);
        }

        List<FileVo> genericFiles = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(folders)) {
            genericFiles.addAll(FileFolderFileVoConvert.INSTANCE.map(folders));
        }
        if (CollectionUtil.isNotEmpty(files)) {
            genericFiles.addAll(UploadFileFileVoConvert.INSTANCE.map(files));
        }

        System.out.println(folders);
        System.out.println("files:");
        System.out.println(files);
        System.out.println(genericFiles);
        Integer skipCount = (curPage - 1) * pageSize;
        Integer totalCount = genericFiles.size(); //获取过滤前总量,让前端知道多少页

        //因为一开始设计问题，导致现在要手动在java逻辑层实现分页
        List<FileVo> sortFiles = genericFiles.stream()
                .sorted(Comparator.comparing(FileVo::getSize).reversed()
                        .thenComparing(FileVo::getGmtCreated).reversed())
                .skip(skipCount)
                .limit(pageSize)
                .collect(Collectors.toList());
        System.out.println("sortFiles");
        System.out.println(sortFiles);
        PageUtils pages = new PageUtils(curPage, pageSize, totalCount, sortFiles);
        return pages;
    }

    /***
     * 用来获取文件下载的URL
     * @param file
     * @param url
     * @return
     */
    public String getFileUrl(UploadFile file, String url) {
        if (file != null) {
            try {
                String fileUUID = file.getUUID();
                // /blogfile?t=2f7838bf548f400bbb58d3986a4e99a8&f=21
                url = url + "/file?t=" + fileUUID + "&f=" + file.getId();
                System.out.println("生成的url为" + url);
                return url;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return "";
    }

    public boolean downloadFile(UploadFile uploadFile, HttpServletRequest request, HttpServletResponse response) throws Exception {
        String id = uploadFile.getId();
        String remotePath = uploadFile.getPath();
        String fileNameTemp = uploadFile.getFullName();
        Integer times = uploadFile.getDownloadCount();
        boolean downloadSuccess = false;
        OutputStream os = null;
        System.out.println("测试测试");
        try {
            os = new BufferedOutputStream(response.getOutputStream());
            //解决下载文件时 中文文件名乱码问题
            //boolean isMSIE = BrowserUtil.isMSBrowser(request);
            //if (isMSIE) {
            //fileNameTemp = URLEncoder.encode(fileNameTemp, "UTF-8");
            //System.out.println("isMSIE" + fileNameTemp);
            //} else {
            // fileNameTemp = new String(fileNameTemp.getBytes("UTF-8"), "UTF-8");
            //System.out.println("notMSIE" + fileNameTemp);
            //}
            //System.out.println("fileNameTemp:" + fileNameTemp);
            response.setCharacterEncoding("utf-8");
            response.setContentType("multipart/form-data");
            //fileNameTemp = URLEncoder.encode(fileNameTemp, "UTF-8");
            response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileNameTemp, "UTF-8"));
            //logger.info("测试下载");
            if (FtpUtil.downloadFile("/" + remotePath, uploadFile.getUUID() + uploadFile.getSuffix(), os)) { // /remotePath/uploadFile.name 无后缀 为文件所在目录  uploadFile.getFullName()
                downloadSuccess = uploadFileService.saveOrUpdate(
                        UploadFile.builder().id(id).downloadCount(times + 1).build());
                //logger.info("文件下载成功!");
            }
            os.flush();
            os.close();
        } finally {
            try {
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return downloadSuccess;
    }

    @Override
    public boolean deleteFile(String fileId) {
        UploadFile uploadFile = uploadFileService.getById(fileId);
        String remotePath = uploadFile.getPath();
        String fullName = uploadFile.getFullName();
        boolean deletedSuccess = FtpUtil.deleteFile("/" + remotePath, uploadFile.getUUID() + uploadFile.getSuffix()); //fullName
        if (deletedSuccess) {
            fileStorageService.removeFileStorageSize(uploadFile.getFileStorageId(), Integer.valueOf(uploadFile.getSize()));
            uploadFileService.removeById(fileId);
        }
        //logger.info("删除文件成功!" + uploadFile);
        return true;
    }

    /**
     * 创建网盘服务
     *
     * @param name      服务名
     * @param serviceId 服务口
     * @return
     */
    @Override
    @Transactional
    public boolean createService(String name, String serviceId) {
        if (existService(serviceId)) {
            return false;
        }
        FileUser fileUser = FileUser.builder().id(serviceId).userId("0").fileStorageId(serviceId).build();
        FileStorage fileStorage = FileStorage.builder().id(serviceId).fileUserId(serviceId).currentSize(0).maxSize(4096 * 1024).permission(FilePermissionEnum.UPLOAD_DOWNLOAD.getStatus()).build();
        FileFolder fileFolder = FileFolder.builder().id(serviceId).fileStorageId(serviceId).parentFolderId(serviceId).gmtCreated(TimeUtil.getNowTimeToSeconds()).name(name).build();
        fileUserService.save(fileUser);
        fileStorageService.save(fileStorage);
        fileFolderService.save(fileFolder);
        return true;
    }

    @Override
    public boolean existService(String serviceId) {
        FileUser fileUser = fileUserService.getById(serviceId);
        FileStorage fileStorage = fileStorageService.getById(serviceId);
        FileFolder fileFolder = fileFolderService.getById(serviceId);
        if (fileUser != null || fileStorage != null || fileFolder != null) {
            //logger.info("创建服务口失败, {}已经被占用", serviceId);
            return true;
        }
        return false;
    }
}
